package map;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import map.CellStatus.CellStatusType;

import org.jgrapht.alg.DijkstraShortestPath;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleDirectedWeightedGraph;

import Model.Car;
import Model.CrossRoads;
import Model.Edge;

public class Map {

	private int size;
	private Cell[][] map;
	private List<Car> cars;
	private List<Entry> entries;
	private HashMap<Point, List<Car>> waitLists;
	private List<Point> randomPos = new ArrayList<Point>();

	public List<Point> getRandomPos() {
		return randomPos;
	}

	public HashMap<Point, List<Car>> getWaitLists() {
		return waitLists;
	}

	private HashMap<Point, CrossRoads> precCrossRoadsForPoint = new HashMap<Point, CrossRoads>();
	private HashMap<Point, CrossRoads> nextCrossRoadsForPoint = new HashMap<Point, CrossRoads>();

	private final int capacity = 50;

	private SimpleDirectedWeightedGraph<CrossRoads, Edge> crossRoadsGraph;

	private DefaultWeightedEdge maxEdge = null;
	private HashMap<Point, CrossRoads> mapForGraph = new HashMap<Point, CrossRoads>();

	public HashMap<Point, CrossRoads> getPrecCrossRoadsForPoint() {
		return precCrossRoadsForPoint;
	}

	public HashMap<Point, CrossRoads> getNextCrossRoadsForPoint() {
		return nextCrossRoadsForPoint;
	}

	public HashMap<Point, CrossRoads> getMapForGraph() {
		return mapForGraph;
	}

	public Map() {
		super();
		this.cars = new ArrayList<Car>();
		this.waitLists = new HashMap<Point, List<Car>>();
		this.size = 18;
		this.map = new Cell[size][];
		for (int i = 0; i < size; i++) {
			map[i] = new Cell[size];
		}
		for (int i = 0; i < size; ++i) {
			for (int j = 0; j < size; ++j) {
				map[i][j] = new Cell(CellStatusType.CROSS, false);
			}
		}
		this.entries = new ArrayList<Entry>();

		this.crossRoadsGraph = new SimpleDirectedWeightedGraph<CrossRoads, Edge>(
				Edge.class);
		Utility u = new Utility();
		u.generateMap(this);

	}

	public void addCarToWaitLists(Car c, Point entry) {
		this.waitLists.get(entry).add(c);
	}

	public Car getFirstCarFromWaitList(Point entry) {
		if (this.waitLists.get(entry).size() > 0)
			return this.waitLists.get(entry).get(0);
		return null;
	}

	public Car removeFirstCarFromWaitLists(Point entry) {
		Car toRemove = this.waitLists.get(entry).get(0);
		this.waitLists.get(entry).remove(0);
		return toRemove;
	}

	public int getWaitListSize(Point entry) {
		return this.waitLists.get(entry).size();
	}

	public List<Entry> getEntries() {
		return entries;
	}

	public Cell[][] getMap() {
		return map;
	}

	public int getSize() {
		return size;
	}

	public List<Car> getCars() {
		return cars;
	}

	public void addCar(Car c) {

		CrossRoads a = getPrecNearestCrossRoads(c.getPosition());
		CrossRoads b = getNextNearestCrossRoads(c.getPosition());

		Edge edge = crossRoadsGraph.getEdge(a, b);
		crossRoadsGraph.setEdgeWeight(edge,
				crossRoadsGraph.getEdgeWeight(edge) + 1);
		this.cars.add(c);
	}

	public void removeCar(Car c) {
		this.cars.remove(c);
	}

	public int getCapacity() {
		return capacity;
	}

	public List<Point> getKeyEntryList() {
		List<Point> res = new ArrayList<Point>();
		res.addAll(this.waitLists.keySet());
		return res;
	}

	public DefaultWeightedEdge getMaxEdge() {
		return maxEdge;
	}

	public void setMaxEdge(DefaultWeightedEdge maxEdge) {
		this.maxEdge = maxEdge;
	}

	public SimpleDirectedWeightedGraph<CrossRoads, Edge> getCrossRoadsGraph() {
		return crossRoadsGraph;
	}

	public CrossRoads getCrossRoadsFromPoint(Point curr) {
		if (mapForGraph.containsKey(curr))
			return mapForGraph.get(curr);
		return null;
	}

	// public void updateEdge(Point carPosition, Point nexCarPosition) {
	// // SE STO USCENDO DA UN ARCO ALLORA AGGIORNO IL PESO
	// if (carPosition.equals(nexCarPosition)) {
	// CrossRoads a = getPrecNearestCrossRoads(carPosition);
	// CrossRoads b = getNextNearestCrossRoads(carPosition);
	// Edge edge = crossRoadsGraph.getEdge(a, b);
	//
	// crossRoadsGraph.setEdgeWeight(edge,
	// crossRoadsGraph.getEdgeWeight(edge) - 1);
	// }else
	// if (map[nexCarPosition.x][nexCarPosition.y].getStatus().equals(
	// CellStatusType.CROSS)
	// && !map[carPosition.x][carPosition.y].getStatus().equals(
	// CellStatusType.CROSS)) {
	// CrossRoads a = getPrecNearestCrossRoads(carPosition);
	// CrossRoads b = getNextNearestCrossRoads(carPosition);
	// Edge edge = crossRoadsGraph.getEdge(a, b);
	//
	// crossRoadsGraph.setEdgeWeight(edge,
	// crossRoadsGraph.getEdgeWeight(edge) - 1);
	// System.out.println("AGGIUNGOOO");
	// }
	// // SE STO ENTRANDO IN UN ARCO
	// else if (!map[nexCarPosition.x][nexCarPosition.y].getStatus().equals(
	// CellStatusType.CROSS)
	// && map[carPosition.x][carPosition.y].getStatus().equals(
	// CellStatusType.CROSS)) {
	// CrossRoads a = getPrecNearestCrossRoads(nexCarPosition);
	// CrossRoads b = getNextNearestCrossRoads(nexCarPosition);
	//
	// Edge edge = crossRoadsGraph.getEdge(a, b);
	// crossRoadsGraph.setEdgeWeight(edge,
	// crossRoadsGraph.getEdgeWeight(edge) + 1);
	// }
	// }

	public List<Point> getPointsListFrom(Point position, Point destination,
			HashMap<Point, CellStatusType> actionForPoint) {
		List<Point> pathTofollow = new ArrayList<Point>();
		CrossRoads start = getNextNearestCrossRoads(position);
		CrossRoads end = getPrecNearestCrossRoads(destination);

		if (start == null || end == null || start.equals(end))
			return new ArrayList<Point>();
		List<Edge> listEdges = DijkstraShortestPath.findPathBetween(
				crossRoadsGraph, start, end);
		if (listEdges.isEmpty()) {
			return new ArrayList<Point>();
		}
		// PRIMO PASSO
		pathTofollow.add(position);
		Point curr = getMoveFrom(position,
				map[position.x][position.y].getStatus());

		while (!map[curr.x][curr.y].getStatus().equals(CellStatusType.CROSS)) {
			pathTofollow.add(curr);
			curr = getMoveFrom(curr, map[position.x][position.y].getStatus());

		}

		// CREO IL PATH
		for (Edge edge : listEdges) {
			addPoints(pathTofollow, actionForPoint,
					pathTofollow.get(pathTofollow.size() - 1), edge
							.getEdgePoints().get(0));
			for (Point p : edge.getEdgePoints()) {
				pathTofollow.add(p);
				if (pathTofollow.get(pathTofollow.size() - 1).equals(end)) {
					pathTofollow.remove(0);
					return pathTofollow;
				}
			}
		}
//		System.out.println("<<<<<<<<" + pathTofollow.size());
		addPoints(pathTofollow, actionForPoint,
				pathTofollow.get(pathTofollow.size() - 1),
				destination);
//		System.out.println("<<<<<<<<" + pathTofollow.size());
		CellStatusType endAction = map[destination.x][destination.y]
				.getStatus();
		if (endAction.equals(CellStatusType.RIGHT)) {
			endAction = CellStatusType.LEFT;
		} else if (endAction.equals(CellStatusType.LEFT)) {
			endAction = CellStatusType.RIGHT;
		} else if (endAction.equals(CellStatusType.UP)) {
			endAction = CellStatusType.DOWN;
		} else if (endAction.equals(CellStatusType.DOWN)) {
			endAction = CellStatusType.UP;
		}
		Point currP = destination;
//		System.out.println(currP +"O- "+ endAction);
		List<Point> tempList = new ArrayList<Point>();
		tempList.add(currP);
		while (!map[currP.x][currP.y].getStatus().equals(CellStatusType.CROSS)) {
			currP = getMoveFrom(currP, endAction);
//			System.out.println(">" + currP);
			tempList.add(currP);
		}
		tempList.remove(currP);
		Collections.reverse(tempList);
		for (Point point : tempList) {
			pathTofollow.add(point);
//			System.out.println(point);
		}
		
		pathTofollow.remove(0);
		return pathTofollow;
	}

	public CrossRoads getPrecNearestCrossRoads(Point point) {
		return precCrossRoadsForPoint.get(point);
	}

	public CrossRoads getNextNearestCrossRoads(Point point) {
		return nextCrossRoadsForPoint.get(point);
	}

	public int getWeightFromEdgeOfPoint(Point point) {
		CrossRoads a = getPrecNearestCrossRoads(point);
		CrossRoads b = getNextNearestCrossRoads(point);
		return (int) crossRoadsGraph.getEdgeWeight(crossRoadsGraph
				.getEdge(a, b));
	}

	private Point getMoveFrom(Point a, CellStatusType action) {
		if (action.equals(CellStatusType.UP))
			return new Point(a.x - 1, a.y);
		if (action.equals(CellStatusType.DOWN))
			return new Point(a.x + 1, a.y);
		if (action.equals(CellStatusType.RIGHT))
			return new Point(a.x, a.y + 1);
		if (action.equals(CellStatusType.LEFT))
			return new Point(a.x, a.y - 1);
		return null;
	}

	private void addPoints(List<Point> pathToFollow,
			HashMap<Point, CellStatusType> actionForPoint, Point a, Point b) {
		CellStatusType action = map[a.x][a.y].getStatus();
		CellStatusType nextAction = map[b.x][b.y].getStatus();

		CellStatusType down = CellStatusType.DOWN;
		CellStatusType up = CellStatusType.UP;
		CellStatusType left = CellStatusType.LEFT;
		CellStatusType right = CellStatusType.RIGHT;
		if (action.equals(CellStatusType.DOWN)) {
			if (action.equals(nextAction)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x + 1, p.y);
				Point p2 = new Point(p1.x + 1, p1.y);
				actionForPoint.put(p, action);
				pathToFollow.add(p1);
				actionForPoint.put(p1, down);
				pathToFollow.add(p2);
				actionForPoint.put(p2, down);
				return;
			}
			if (nextAction.equals(CellStatusType.LEFT)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x + 1, p.y);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, left);
				return;
			}

			if (nextAction.equals(CellStatusType.RIGHT)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x + 1, p.y);
				Point p2 = new Point(p1.x + 1, p1.y);
				Point p3 = new Point(p2.x, p2.y + 1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, down);
				pathToFollow.add(p2);
				actionForPoint.put(p2, right);
				pathToFollow.add(p3);
				actionForPoint.put(p3, right);
				return;
			}
		}

		if (action.equals(CellStatusType.UP)) {
			if (action.equals(nextAction)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x - 1, p.y);
				Point p2 = new Point(p1.x - 1, p1.y);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, up);
				pathToFollow.add(p2);
				actionForPoint.put(p2, up);
				return;
			}
			if (nextAction.equals(CellStatusType.RIGHT)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x - 1, p.y);
				actionForPoint.put(p, action);
				pathToFollow.add(p1);
				actionForPoint.put(p1, right);
				return;
			}

			if (nextAction.equals(CellStatusType.LEFT)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x - 1, p.y);
				Point p2 = new Point(p1.x - 1, p1.y);
				Point p3 = new Point(p2.x, p2.y - 1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, up);
				pathToFollow.add(p2);
				actionForPoint.put(p2, left);
				pathToFollow.add(p3);
				actionForPoint.put(p3, left);
				return;
			}
		}

		if (action.equals(CellStatusType.RIGHT)) {
			if (action.equals(nextAction)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y + 1);
				Point p2 = new Point(p1.x, p1.y + 1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, right);
				pathToFollow.add(p2);
				actionForPoint.put(p2, right);
				return;
			}
			if (nextAction.equals(CellStatusType.UP)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y + 1);
				Point p2 = new Point(p1.x, p1.y + 1);
				Point p3 = new Point(p2.x - 1, p2.y);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, right);
				pathToFollow.add(p2);
				actionForPoint.put(p2, up);
				pathToFollow.add(p3);
				actionForPoint.put(p3, up);
				return;
			}
			if (nextAction.equals(CellStatusType.DOWN)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y+1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, down);
				return;
			}
		}
		if (action.equals(CellStatusType.LEFT)) {
			if (action.equals(nextAction)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y - 1);
				Point p2 = new Point(p1.x, p1.y - 1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, left);
				pathToFollow.add(p2);
				actionForPoint.put(p2, left);
				return;
			}
			if (nextAction.equals(CellStatusType.DOWN)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y - 1);
				Point p2 = new Point(p1.x, p1.y - 1);
				Point p3 = new Point(p2.x + 1, p2.y);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, left);
				pathToFollow.add(p2);
				actionForPoint.put(p2, down);
				pathToFollow.add(p3);
				actionForPoint.put(p3, down);
				return;
			}
			if (nextAction.equals(CellStatusType.UP)) {
				Point p = pathToFollow.get(pathToFollow.size() - 1);
				Point p1 = new Point(p.x, p.y - 1);
				pathToFollow.add(p1);
				actionForPoint.put(p, action);
				actionForPoint.put(p1, up);
				return;
			}
		}
	}

}
